/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation
 * 	  David Corbin  - added Revert action (now RevertFromChangelistAction)
 ******************************************************************************/
package net.sourceforge.perforce.ui.views;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.sourceforge.perforce.core.IResourceStateChangeListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.ResourceStateChangeAdapter;
import net.sourceforge.perforce.core.api.IPerforceListener;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.resources.IP4File;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;
import net.sourceforge.perforce.ui.IPerforceUIConstants;
import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;
import net.sourceforge.perforce.ui.internal.dnd.LocalFileTransfer;
import net.sourceforge.perforce.ui.internal.dnd.PendingChangelistDragAdapter;
import net.sourceforge.perforce.ui.internal.dnd.PendingChangelistDropAdapter;
import net.sourceforge.perforce.ui.internal.model.ClientPendingChangelistContainer;
import net.sourceforge.perforce.ui.internal.model.OtherPendingChangelistContainer;
import net.sourceforge.perforce.ui.internal.model.PendingChangelistCache;
import net.sourceforge.perforce.ui.model.AllRootsElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;

/**
  * The PendingChangelistView defines a view for managing Perforce changelists.
  * Compare with P4Win.
  * 
  * @version $Revision: 225 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author  <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class PendingChangelistView extends ViewPart implements IPerforceListener
{
	// ---------------------------------------------------------------------------- Public Constants
	
	/** The view's identifier. */
	public static final String ID = 
		PerforceUIPlugin.ID + ".views.PendingChangelistView";                          //$NON-NLS-1$
	
	// -------------------------------------------------------------------- Private Static Variables
	
	/** Stores the view's singleton instance. */
	private static PendingChangelistView instance;
	
	// --------------------------------------------------------------------------- Private Variables

	/** Stores the Pending Changelist Caches for the servers. */
	private Map pendingChangelistCaches = new HashMap(); 
	/** Holds the view's root element. */
	private AllRootsElement root;
	/** Stores the tree viewer used to display the pending changelist tree. */
	private InternalTreeViewer viewer;	
	/** Stores the DrillDown adapter. */
	private DrillDownAdapter drillPart;
	/** Stores the RefreshAction. */
	private RefreshAction refreshAction;
	
	/** Resource State Change Listener. */
	private IResourceStateChangeListener changeListener;

	// ----------------------------------------------------------------------- Public Static Methods
	
	/** Call this method to retrieve the PendingChangeListView singleton.
	  *
	  * @return the singleton. 
	  */
	public static final PendingChangelistView getInstance()
	{
		return instance;
	}
	
	// ------------------------------------------------------------------------- Public Constructors
	
	/** Constructor for PendingChangelistView. */
	public PendingChangelistView()
	{
		instance = this;
	}
	
	// ------------------------------------------------------------------------------ Public Methods
	
	/** @see IWorkbenchPart#createPartControl(Composite) */
	public void createPartControl(Composite parent)
	{
		root = new AllRootsElement();
		
		DecoratingLabelProvider labelProvider =
			new DecoratingLabelProvider(
				new WorkbenchLabelProvider(), 
				new ChangelistViewDecorator());
		
		viewer = new InternalTreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(new ContentProvider());	
		viewer.setLabelProvider(labelProvider);
		viewer.setSorter(new InternalSorter());
		viewer.setInput(root);		
		viewer.getControl().addKeyListener(new KeyAdapter() 
		{
			public void keyPressed(KeyEvent event) 
			{
				if (event.keyCode == SWT.F5)
				{
					refreshAction.run();
				}
			}
		});
			
		drillPart = new DrillDownAdapter(viewer);
									
		getSite().setSelectionProvider(viewer);			
		contributeActions();
		initDragAndDrop();		
		
		changeListener = new ResourceStateChangeAdapter() 
		{
			/** @see ResourceStateChangeAdapter#resourceStateChanged(IResource) */
			public void resourceStateChanged(IResourceStateDelta[] resourceStateDeltas) 
			{
				IPerforceServerManager manager = PerforceCorePlugin.getServerManager();		
						
				final Set serverSet = new HashSet();
				for (int i = 0; i < resourceStateDeltas.length; i++)
				{
					try
					{
						IResource resource = resourceStateDeltas[i].getResource();
						IPerforceServer server = manager.getPerforceServer(resource);
						serverSet.add(server);
					}
					catch (PerforceException e)
					{
						PerforceUIPlugin.log(e.getStatus());
					}
				}
			
			
				Shell shell = getSite().getShell();
				shell.getDisplay().asyncExec(new Runnable()
				{
					public void run() 
					{
						for (Iterator it = serverSet.iterator(); it.hasNext();)
						{
							internalRefresh(it.next());
						}
					}
				});
			}

			public void changelistStateChanged(IChangelist[] changelists) 
			{
				final Set serverSet = new HashSet();
				for (int i = 0; i < changelists.length; i++)
				{
					serverSet.add(changelists[i].getServer());
				}
				
				Shell shell = getSite().getShell();
				shell.getDisplay().asyncExec(new Runnable()
				{
					public void run() 
					{
						for (Iterator it = serverSet.iterator(); it.hasNext();)
						{
							internalRefresh(it.next());
						}
					}
				});
			}
			
			public void changelistStateChanged(final IPerforceServer[] servers) 
			{
				Shell shell = getSite().getShell();
				shell.getDisplay().asyncExec(new Runnable()
				{
					public void run() 
					{
						for (int i = 0; i < servers.length; i++)
						{
							internalRefresh(servers[i]);
						}
					}
				});
			}
		};
		PerforceCorePlugin.getPlugin().addResourceStateChangeListener(changeListener);
		PerforceCorePlugin.getServerManager().addPerforceListener(this);
		
		viewer.setInternalTreeViewerListener(new InternalTreeViewerListenerAdapter()
		{
			public void afterTreeCollapsed(TreeEvent event) 
			{
				Object element = event.item.getData();
				if (element instanceof OtherPendingChangelistContainer)
				{
					OtherPendingChangelistContainer container = 
						(OtherPendingChangelistContainer) element;
						
					IPerforceServer server = container.getServer();
					PendingChangelistCache cache = getPendingChangelistCache(server);
					cache.setPerformOpenedAll(false);
				}
				else
				if (element instanceof IPerforceServer)
				{
					IPerforceServer server = (IPerforceServer) element;
					PendingChangelistCache cache = getPendingChangelistCache(server);
					cache.setPerformOpenedAll(false);
				}
			}

			public void beforeTreeExpanded(TreeEvent event) 
			{
				Object element = event.item.getData();
				if (element instanceof OtherPendingChangelistContainer)
				{
					final OtherPendingChangelistContainer container = 
						(OtherPendingChangelistContainer) element;
						
					IPerforceServer server = container.getServer();
					PendingChangelistCache cache = getPendingChangelistCache(server);
					cache.setPerformOpenedAll(true);
					cache.invalidate();
				}
			}
		});
	}

	/** @see IWorkbenchPart#setFocus() */
	public void setFocus()
	{
		viewer.getControl().setFocus();
	}
	
	/** @see org.eclipse.ui.IWorkbenchPart#dispose() */
	public void dispose() 
	{
		PerforceCorePlugin.getPlugin().removeResourceStateChangeListener(changeListener);
		PerforceCorePlugin.getServerManager().removePerforceListener(this);
		
		viewer.getLabelProvider().dispose();		
		super.dispose();
	}

	// ---------------------------------------------------------- Public Methods (IPerforceListener)
	
	/** @see IPerforceListener#perforceServerAdded(IPerforceServer) */
	public void perforceServerAdded(IPerforceServer server) 
	{
		Display display = getSite().getShell().getDisplay();
		display.asyncExec(new Runnable() 
		{
			public void run() 
			{
				internalRefresh();
			}
		});
	}

	/** @see IPerforceListener#perforceServerRemoved(IPerforceServer) */
	public void perforceServerRemoved(IPerforceServer server) 
	{		
		Display display = getSite().getShell().getDisplay();
		display.asyncExec(new Runnable() 
		{
			public void run() 
			{
				internalRefresh();
			}
		});
	}
	
	// ----------------------------------------------------------------------------- Private Methods
	
	/** Called to refresh the view. Invaliadtes all PendingChangelistCaches. */
	private void internalRefresh()
	{
		for (Iterator it = pendingChangelistCaches.values().iterator(); it.hasNext();)
		{
			PendingChangelistCache cache = (PendingChangelistCache) it.next();
			cache.invalidate(); 
		}
		
		viewer.refresh();
	}
	
	/** Called to refresh the given view element. Invalidates the responsible 
	  * PendingChangelistCache.
	  * 
	  * @param o the object to refresh.
	  */
	private void internalRefresh(Object o)
	{
		IPerforceServer server = null;
		if (o instanceof IPerforceServer)
		{
			server = (IPerforceServer) o;
		}
		else
		if (o instanceof IChangelist)
		{
			server = ((IChangelist) o).getServer();
		}
		else
		if (o instanceof IP4File)
		{
			server = ((IP4File) o).getServer();
		}
		
		if (server != null)
		{
			PendingChangelistCache cache = 
				(PendingChangelistCache) pendingChangelistCaches.get(server);
			cache.invalidate();
		}
		
		viewer.refresh(o);
	} 
	
	/** Called to retrieve the PendingChangelistCache for the given server.
	  *
	  * @param server the server.
	  * @return the server's PendingChangelistCache. 
	  */
	private PendingChangelistCache getPendingChangelistCache(IPerforceServer server)
	{
		PendingChangelistCache result = 
			(PendingChangelistCache) pendingChangelistCaches.get(server);
		if (result == null)
		{
			result = new PendingChangelistCache(server);
			pendingChangelistCaches.put(server, result);
		}
		
		return result;
	}
	
	/** Adds drag and drop support to the pending changelists view. */
	private void initDragAndDrop() 
	{
		int operations = DND.DROP_MOVE;
		Transfer[] transfers = new Transfer[]
		{ 
			LocalFileTransfer.getInstance() 
		};
		
		PendingChangelistDragAdapter dragAdapter = new PendingChangelistDragAdapter(viewer);		
		viewer.addDragSupport(operations, transfers, dragAdapter);
		
		PendingChangelistDropAdapter dropAdapter = new PendingChangelistDropAdapter(viewer);
		dropAdapter.setFeedbackEnabled(false);
		viewer.addDropSupport(operations, transfers, dropAdapter);
	}	

	/** 
	  * Contribute actions to the view. 
	  */
	private void contributeActions() 
	{
		viewer.addDoubleClickListener(new DoubleClickListener());
		
		refreshAction = new RefreshAction();		

		// Create the popup menu
		MenuManager menuMgr = new MenuManager();
		Tree tree = viewer.getTree();
		Menu menu = menuMgr.createContextMenu(tree);
		menuMgr.addMenuListener(new IMenuListener() 
		{
			public void menuAboutToShow(IMenuManager manager) 
			{	
				manager.add(new Separator("perforce.group1"));
				manager.add(new Separator(IWorkbenchActionConstants.SEP));
				manager.add(new Separator("perforce.group2"));
				manager.add(new Separator(IWorkbenchActionConstants.SEP));				
				manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
				manager.add(refreshAction);				
			}
		});
		menuMgr.setRemoveAllWhenShown(true);
		tree.setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	
		// Create the local tool bar
		IActionBars bars = getViewSite().getActionBars();
		IToolBarManager tbm = bars.getToolBarManager();
		drillPart.addNavigationActions(tbm);
		tbm.add(refreshAction);
		tbm.update(false);	
	}

	// ----------------------------------------------------------------------- Private Inner Classes
	
	/** Handler class for double clicks.
	  *
	  * TODO: (re-) add double click of LocalFile. 
	  */
	private class DoubleClickListener implements IDoubleClickListener
	{
		/** @see IDoubleClickListener#doubleClick(DoubleClickEvent) */
		public void doubleClick(DoubleClickEvent event) 
		{
			IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
			IAdaptable element = (IAdaptable) selection.getFirstElement();
			
			if (element instanceof IP4File)
			{
				doubleClickLocalFile((IP4File) element);
			}
			else
			{
				viewer.expandToLevel(element, 1);
			}			
		}
		
		/** Performs handling of double clicks on LocalFile elements.
		  * 
		  * @param localFile the LocalFile instance
		  */
		private void doubleClickLocalFile(IP4File localFile) 
		{
			IWorkspaceRoot root = PerforceUIPlugin.getWorkspace().getRoot();
			IFile file = root.getFileForLocation(localFile.getLocation());
			if (file == null || !file.isAccessible())
			{
				return;
			}
			
			IWorkbench workbench = PerforceUIPlugin.getPlugin().getWorkbench();					
			IEditorRegistry registry = workbench.getEditorRegistry();
			IWorkbenchPage page = workbench.getActiveWorkbenchWindow().getActivePage();
			
			IEditorDescriptor descriptor = registry.getDefaultEditor(file);					
			
			String editorId;
			if (descriptor == null) 
			{
				editorId = "org.eclipse.ui.DefaultTextEditor";                         //$NON-NLS-1$
			} 
			else 
			{
				editorId = descriptor.getId();
			}		
				
			try 
			{
				page.openEditor(file, editorId);
			} 
			catch (PartInitException e) 
			{
				PerforceUIPlugin.log(e.getStatus());
			}					
		}
	};

	/** Action class for refreshing the view. */
	private class RefreshAction extends Action
	{
		// ---------------------------------------------------------------------------- Constructors
		
		/** Constructor for RefreshAction. */
		RefreshAction()
		{
			super(Policy.bind("PendingChangelistView.refreshAction.label"));           //$NON-NLS-1$

			setImageDescriptor(
				PerforceUIPlugin.getPlugin().getImageDescriptor(IPerforceUIConstants.IMG_REFRESH));
		}
		
		// -------------------------------------------------------------------------- Public Methods
		
		/** @see Action#run() */
		public void run() 
		{
			internalRefresh();
		}	
	};
	
	/**
	  * This class makes the tree view a little "smarter" ;)
	  */
	private class ContentProvider extends WorkbenchContentProvider
	{	
		/** Stores a mapping between server and children. */
		private Map childMap = new HashMap();
			
		/** @see ITreeContentProvider#hasChildren(Object) */
		public boolean hasChildren(Object element)
		{
			if (element instanceof ClientPendingChangelistContainer ||
			    element instanceof OtherPendingChangelistContainer)
			{			
				return true;
			}
			
			return super.hasChildren(element);			
		}
		
		/** @see org.eclipse.ui.model.WorkbenchContentProvider#getChildren(Object) */
		public Object[] getChildren(Object element) 
		{
			if (!(element instanceof IPerforceServer))
			{
				return super.getChildren(element);	
			}
									
			IPerforceServer server = (IPerforceServer) element;
			Object[] result = (Object[]) childMap.get(server);
			if (result == null)			
			{
				PendingChangelistCache cache = getPendingChangelistCache(server);
				result = new Object[] 
				{ 
					new ClientPendingChangelistContainer(server, cache), 
					new OtherPendingChangelistContainer(server, cache),
				};
				childMap.put(server, result);
			}
						
			return result;
		}
	}	

	/**
	  * Class responsible for sorting the tree.
	  */
	private static class InternalSorter extends ViewerSorter
	{
		/** @see ViewerSorter#compare(Viewer, Object, Object) */
		public int compare(Viewer viewer, Object e1, Object e2)
		{
			if (e1 instanceof IPerforceServer && e2 instanceof IPerforceServer)
			{
				IPerforceServer server1 = (IPerforceServer) e1;
				IPerforceServer server2 = (IPerforceServer) e2;
				
				return server1.getUniqueName().compareTo(server2.getUniqueName());
			}
			else
			if (e1 instanceof IChangelist && e2 instanceof IChangelist)
			{				
				IChangelist changelist1 = (IChangelist) e1;
				IChangelist changelist2 = (IChangelist) e2;			
					
				return changelist1.getId().intValue() - changelist2.getId().intValue();
			}
			else
			{
				return super.compare(viewer, e1, e2);
			}
		}
	}
}
